﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Net;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Web;

namespace AmazedSaint.Vingy.ViewModels
{

    /// <summary>
    /// Few utility extensions to clean up the results
    /// </summary>
    public static class UtilityStringExtensions
    {
        public static string StripIllegalChars(this string s)
        {
            if (string.IsNullOrEmpty(s) || string.IsNullOrWhiteSpace(s))
                return string.Empty;

            return s.Replace("", "").Replace("", "");
        }
    }

    /// <summary>
    /// A View Model to do the search and hold the results, with some basic support for paging
    /// </summary>
    public class WebSearchViewModel : INotifyPropertyChanged
    {

        #region Constants

        const string appId = "BA598F2D1A35E4F1D9D62C92499691BB166F4494";
        const int pageSize = 30;

        #endregion

        #region Event
        public event EventHandler ResultsModified = delegate { };
        #endregion

        #region Properties


        public Visibility IsLoading { get; set; }

        public ICommand SearchCommand { get; set; }

        public ICommand NextCommand { get; set; }

        public ICommand PrevCommand { get; set; }

        public List<SearchResult> Results { get; set; }

        public string Errors { get; set; }

        public Visibility HasErrors { get; set; }

        public int Offset { get; set; }


        private int _srcIndex;
        public int SourceIndex
        {
            get
            {
                return _srcIndex;
            }
            set
            {
                _srcIndex = value;
                DoAsync(() => Search());
            }
        }


        public int Total { get; set; }

        public int OffsetEnd
        {
            get
            {
                if (Total != 0)
                    return Offset + pageSize;
                else
                    return 0;

            }
        }

        public string[] Sources
        {
            get
            {
                return new string[] { "Web","Stackoverflow","MSDN","Microsoft","CodeProject","CodePlex",
                    "15Seconds","DotNetKicks","DZone","Reddit", "Koders"};
            }
        }

        public string Data { get; set; }

        public string Input { get; set; }

        #endregion

        #region Ctor and Methods


        public WebSearchViewModel()
        {
            Results = new List<SearchResult>();
            Errors = string.Empty;
            HasErrors = Visibility.Collapsed;
            SourceIndex = 0;
            SearchCommand = new AsyncDelegateCommand(() => this.Search());
            NextCommand = new AsyncDelegateCommand(() => this.Next());
            PrevCommand = new AsyncDelegateCommand(() => this.Previous());
            IsLoading = Visibility.Collapsed;
            Input = string.Empty;
        }

        public void DoAsync(Action action)
        {
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += (e, s) => action();
            worker.RunWorkerAsync();
        }


        public void Search()
        {
            if (!string.IsNullOrEmpty(Input))
                Search(Input);
        }


        public void Search(string data)
        {
            Data = data;
            HttpWebRequest request = BuildRequest(data, 0);
            FetchResponse(request);
        }

        public void Previous()
        {
            HttpWebRequest request = BuildRequest(Data, Offset - Results.Count);
            FetchResponse(request);

        }

        public void Next()
        {
            HttpWebRequest request = BuildRequest(Data, Offset + Results.Count);
            FetchResponse(request);

        }

        private void FetchResponse(HttpWebRequest request)
        {

            IsLoading = Visibility.Visible;
            OnPropertyChanged("IsLoading");
            Errors = string.Empty;
            try
            {
                // Send the request; display the response.
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                GrabResponse(response);

            }
            catch (WebException ex)
            {
                // An exception occurred while accessing the network.
                Debug.WriteLine(ex.Message);
                Results = new List<SearchResult>();
                Errors += "Unable to fetch the results. " + ex.Message + Environment.NewLine;
                HasErrors = Visibility.Visible;
                Offset = 0;
                Total = 0;
            }

            IsLoading = Visibility.Collapsed;
            OnPropertyChanged("IsLoading");
            OnPropertyChanged("Total");
            OnPropertyChanged("Offset");
            OnPropertyChanged("OffsetEnd");
            OnPropertyChanged("Results");
            OnPropertyChanged("Errors");
            OnPropertyChanged("HasErrors");

            ResultsModified(this, new EventArgs());
        }



        HttpWebRequest BuildRequest(string data, int offset)
        {
            if (offset < 0) offset = 0;

            string src = Sources[SourceIndex] == "Web" ? string.Empty : " site:" + Sources[SourceIndex] + ".com";

            string requestString = "http://api.bing.net/xml.aspx?"

                // Common request fields (required)
                + "AppId=" + appId
                + "&Query=" + HttpUtility.UrlEncode(data) + src
                + "&Sources=Web"

                // Common request fields (optional)
                + "&Version=2.0"
                + "&Market=en-us"
                + "&Adult=Moderate"
                + "&Options=EnableHighlighting"

                // Web-specific request fields (optional)
                + "&Web.Count=" + pageSize
                + "&Web.Offset=" + offset
                + "&Web.Options=DisableHostCollapsing+DisableQueryAlterations";

            // Create and initialize the request.
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(
                requestString);

            return request;
        }

        void GrabResponse(HttpWebResponse response)
        {
            // Load the response into an XmlDocument.
            XmlDocument document = new XmlDocument();
            document.Load(response.GetResponseStream());

            // Add the default namespace to the namespace manager.
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(
                document.NameTable);
            nsmgr.AddNamespace(
                "api",
                "http://schemas.microsoft.com/LiveSearch/2008/04/XML/element");

            XmlNodeList errors = document.DocumentElement.SelectNodes(
                "./api:Errors/api:Error",
                nsmgr);



            if (errors.Count > 0)
            {
                // There are errors in the response. Display error details.
                HasErrors = Visibility.Visible;
                DisplayErrors(errors);
                Results = new List<SearchResult>();
            }
            else
            {
                // There were no errors in the response. Display the
                // Web results.
                HasErrors = Visibility.Collapsed;
                Errors = string.Empty;
                DisplayResults(document.DocumentElement, nsmgr);
            }
        }

        void DisplayResults(XmlNode root, XmlNamespaceManager nsmgr)
        {
            // Add the Web SourceType namespace to the namespace manager.
            nsmgr.AddNamespace(
                "web",
                "http://schemas.microsoft.com/LiveSearch/2008/04/XML/web");

            XmlNode web = root.SelectSingleNode("./web:Web", nsmgr);
            XmlNodeList results = web.SelectNodes(
                "./web:Results/web:WebResult",
                nsmgr);

            string version = GetNodeInnerText(root, "./@Version", nsmgr);
            string searchTerms = GetNodeInnerText(root, "./api:Query/api:SearchTerms", nsmgr);
            int offset;
            int.TryParse(GetNodeInnerText(web, "./web:Offset", nsmgr),
                out offset);
            int total;
            int.TryParse(GetNodeInnerText(web, "./web:Total", nsmgr),
                out total);

            this.Total = total;
            this.Offset = offset;

            // Display the results header.
            var listResults = new ObservableCollection<SearchResult>();

            // Display the Web results.
            foreach (XmlNode result in results)
            {

                var res = new SearchResult();

                res.Title = GetNodeInnerText(result, "./web:Title", nsmgr).StripIllegalChars();
                res.URL = GetNodeInnerText(result, "./web:Url", nsmgr);
                res.Description = GetNodeInnerText(result, "./web:Description", nsmgr).StripIllegalChars();
                res.Date = GetNodeInnerText(result, "./web:DateTime", nsmgr);

                listResults.Add(res);
            }

            this.Results = new List<SearchResult>(listResults);


        }

        static string GetNodeInnerText(XmlNode root, string xpath, XmlNamespaceManager nsmgr)
        {
            XmlNode childNode = root.SelectSingleNode(xpath, nsmgr);
            return (childNode == null) ? null : childNode.InnerText;
        }


        void DisplayErrors(XmlNodeList errors)
        {
            string listErrors = string.Empty;

            // Iterate over the list of errors and display error details.
            foreach (XmlNode error in errors)
            {
                foreach (XmlNode detail in error.ChildNodes)
                {
                    listErrors += detail.Name + ": " + detail.InnerText + Environment.NewLine;
                }
            }

            this.Errors = listErrors;

        }



        public event PropertyChangedEventHandler PropertyChanged;

        void OnPropertyChanged(string prop)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
            }
        }

        #endregion
    }
}
